From 06716a6c79d614bc1b7607e46546b8d14f5b51e2 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 16 Feb 2014 22:57:24 -0500 Subject: [PATCH] Add center widget support to GtkBox This makes GtkCenterBox unnecessary, and at the same time adds more features: the center widget can be expanded, and baseline alignment is supported. --- docs/reference/gtk/gtk3-sections.txt | 2 + gtk/gtkbox.c | 482 ++++++++++++++++++++++++++- gtk/gtkbox.h | 6 + 3 files changed, 476 insertions(+), 14 deletions(-) diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt index c25f40f9f1..72ed1f0509 100644 --- a/docs/reference/gtk/gtk3-sections.txt +++ b/docs/reference/gtk/gtk3-sections.txt @@ -494,6 +494,8 @@ gtk_box_query_child_packing gtk_box_set_child_packing gtk_box_get_baseline_position gtk_box_set_baseline_position +gtk_box_get_center_widget +gtk_box_set_center_widget GTK_BOX GTK_IS_BOX diff --git a/gtk/gtkbox.c b/gtk/gtkbox.c index 16ef16cff1..72616efdea 100644 --- a/gtk/gtkbox.c +++ b/gtk/gtkbox.c @@ -105,9 +105,12 @@ enum { CHILD_PROP_POSITION }; +typedef struct _GtkBoxChild GtkBoxChild; + struct _GtkBoxPrivate { GList *children; + GtkBoxChild *center; GtkOrientation orientation; gint16 spacing; @@ -118,8 +121,6 @@ struct _GtkBoxPrivate guint baseline_pos : 2; }; -typedef struct _GtkBoxChild GtkBoxChild; - /* * GtkBoxChild: * @widget: the child widget, packed into the GtkBox. @@ -458,8 +459,8 @@ count_expand_children (GtkBox *box, } static void -gtk_box_size_allocate (GtkWidget *widget, - GtkAllocation *allocation) +gtk_box_size_allocate_no_center (GtkWidget *widget, + GtkAllocation *allocation) { GtkBox *box = GTK_BOX (widget); GtkBoxPrivate *private = box->priv; @@ -790,6 +791,382 @@ gtk_box_size_allocate (GtkWidget *widget, } } +static void +gtk_box_size_allocate_with_center (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkBox *box = GTK_BOX (widget); + GtkBoxPrivate *priv = box->priv; + GtkBoxChild *child; + GList *children; + gint nvis[2]; + gint nexp[2]; + GtkTextDirection direction; + GtkAllocation child_allocation; + GtkRequestedSize *sizes[2]; + GtkRequestedSize center_req; + gint child_minimum_baseline, child_natural_baseline; + gint minimum_above, natural_above; + gint minimum_below, natural_below; + gboolean have_baseline; + gint baseline; + gint idx[2]; + gint center_pos; + gint center_size; + gint box_size; + gint side[2]; + GtkPackType packing; + gint min_size[2]; + gint nat_size[2]; + gint extra[2]; + gint n_extra_widgets[2]; + gint x = 0, y = 0, i; + gint child_size; + + gtk_widget_set_allocation (widget, allocation); + + nvis[0] = nvis[1] = 0; + nexp[0] = nexp[1] = 0; + for (children = priv->children; children; children = children->next) + { + child = children->data; + + if (child != priv->center && + gtk_widget_get_visible (child->widget)) + { + nvis[child->pack] += 1; + if (child->expand || gtk_widget_compute_expand (child->widget, priv->orientation)) + nexp[child->pack] += 1; + } + } + + direction = gtk_widget_get_direction (widget); + sizes[0] = g_newa (GtkRequestedSize, nvis[0]); + sizes[1] = g_newa (GtkRequestedSize, nvis[1]); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + box_size = allocation->width; + else + box_size = allocation->height; + + have_baseline = FALSE; + minimum_above = natural_above = 0; + minimum_below = natural_below = 0; + + min_size[0] = nat_size[0] = nvis[0] * priv->spacing; + min_size[1] = nat_size[1] = nvis[1] * priv->spacing; + + /* Retrieve desired size for visible children. */ + idx[0] = idx[1] = 0; + for (children = priv->children; children; children = children->next) + { + GtkRequestedSize *req; + + child = children->data; + + if (!gtk_widget_get_visible (child->widget)) + continue; + + if (child == priv->center) + req = ¢er_req; + else + req = &(sizes[child->pack][idx[child->pack]]); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + gtk_widget_get_preferred_width_for_height (child->widget, + allocation->height, + &req->minimum_size, + &req->natural_size); + else + gtk_widget_get_preferred_height_and_baseline_for_width (child->widget, + allocation->width, + &req->minimum_size, + &req->natural_size, + NULL, NULL); + + if (child != priv->center) + { + min_size[child->pack] += req->minimum_size + 2 * child->padding; + nat_size[child->pack] += req->natural_size + 2 * child->padding; + } + + req->data = child; + + idx[child->pack] += 1; + } + + /* Determine size of center */ + if (priv->center->expand) + center_size = MAX (box_size - 2 * MAX (nat_size[0], nat_size[1]), center_req.minimum_size); + else + center_size = MAX (MIN (center_req.natural_size, box_size - min_size[0] - min_size[1]), center_req.minimum_size); + + if (priv->homogeneous) + { + extra[0] = ((box_size - center_size) / 2 - nvis[0] * priv->spacing) / nvis[0]; + extra[1] = ((box_size - center_size) / 2 - nvis[1] * priv->spacing) / nvis[1]; + extra[0] = MIN (extra[0], extra[1]); + n_extra_widgets[0] = 0; + } + else + { + for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) + { + gint s; + /* Distribute the remainder naturally on each side */ + s = MIN ((box_size - center_size) / 2 - min_size[packing], box_size - center_size - min_size[0] - min_size[1]); + s = gtk_distribute_natural_allocation (MAX (0, s), nvis[packing], sizes[packing]); + + /* Calculate space which hasn't distributed yet, + * and is available for expanding children. + */ + if (nexp[packing] > 0) + { + extra[packing] = s / nexp[packing]; + n_extra_widgets[packing] = s % nexp[packing]; + } + else + extra[packing] = 0; + } + } + + /* Allocate child sizes. */ + for (packing = GTK_PACK_START; packing <= GTK_PACK_END; ++packing) + { + for (i = 0, children = priv->children; children; children = children->next) + { + child = children->data; + + /* If widget is not visible, skip it. */ + if (!gtk_widget_get_visible (child->widget)) + continue; + + /* Skip the center widget */ + if (child == priv->center) + continue; + + /* If widget is packed differently, skip it. */ + if (child->pack != packing) + continue; + + /* Assign the child's size. */ + if (priv->homogeneous) + { + child_size = extra[0]; + + if (n_extra_widgets[0] > 0) + { + child_size++; + n_extra_widgets[0]--; + } + } + else + { + child_size = sizes[packing][i].minimum_size + child->padding * 2; + + if (child->expand || gtk_widget_compute_expand (child->widget, priv->orientation)) + { + child_size += extra[packing]; + + if (n_extra_widgets[packing] > 0) + { + child_size++; + n_extra_widgets[packing]--; + } + } + } + + sizes[packing][i].natural_size = child_size; + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL && + gtk_widget_get_valign_with_baseline (child->widget) == GTK_ALIGN_BASELINE) + { + gint child_allocation_width; + gint child_minimum_height, child_natural_height; + + if (child->fill) + child_allocation_width = MAX (1, child_size - child->padding * 2); + else + child_allocation_width = sizes[packing][i].minimum_size; + + child_minimum_baseline = -1; + child_natural_baseline = -1; + gtk_widget_get_preferred_height_and_baseline_for_width (child->widget, + child_allocation_width, + &child_minimum_height, &child_natural_height, + &child_minimum_baseline, &child_natural_baseline); + + if (child_minimum_baseline >= 0) + { + have_baseline = TRUE; + minimum_below = MAX (minimum_below, child_minimum_height - child_minimum_baseline); + natural_below = MAX (natural_below, child_natural_height - child_natural_baseline); + minimum_above = MAX (minimum_above, child_minimum_baseline); + natural_above = MAX (natural_above, child_natural_baseline); + } + } + + i++; + } + } + + baseline = gtk_widget_get_allocated_baseline (widget); + if (baseline == -1 && have_baseline) + { + gint height = MAX (1, allocation->height); + + /* TODO: This is purely based on the minimum baseline, when things fit we should + * use the natural one? + */ + switch (priv->baseline_pos) + { + case GTK_BASELINE_POSITION_TOP: + baseline = minimum_above; + break; + case GTK_BASELINE_POSITION_CENTER: + baseline = minimum_above + (height - (minimum_above + minimum_below)) / 2; + break; + case GTK_BASELINE_POSITION_BOTTOM: + baseline = height - minimum_below; + break; + } + } + + /* Allocate child positions. */ + for (packing = GTK_PACK_START; packing <= GTK_PACK_END; ++packing) + { + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + child_allocation.y = allocation->y; + child_allocation.height = MAX (1, allocation->height); + if (packing == GTK_PACK_START) + x = allocation->x; + else + x = allocation->x + allocation->width; + } + else + { + child_allocation.x = allocation->x; + child_allocation.width = MAX (1, allocation->width); + if (packing == GTK_PACK_START) + y = allocation->y; + else + y = allocation->y + allocation->height; + } + + for (i = 0, children = priv->children; children; children = children->next) + { + child = children->data; + + /* If widget is not visible, skip it. */ + if (!gtk_widget_get_visible (child->widget)) + continue; + + /* Skip the center widget */ + if (child == priv->center) + continue; + + /* If widget is packed differently, skip it. */ + if (child->pack != packing) + continue; + + child_size = sizes[packing][i].natural_size; + + /* Assign the child's position. */ + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + if (child->fill) + { + child_allocation.width = MAX (1, child_size - child->padding * 2); + child_allocation.x = x + child->padding; + } + else + { + child_allocation.width = sizes[packing][i].minimum_size; + child_allocation.x = x + (child_size - child_allocation.width) / 2; + } + + if (packing == GTK_PACK_START) + { + x += child_size + priv->spacing; + } + else + { + x -= child_size + priv->spacing; + child_allocation.x -= child_size; + } + + if (direction == GTK_TEXT_DIR_RTL) + child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width; + + } + else /* (private->orientation == GTK_ORIENTATION_VERTICAL) */ + { + if (child->fill) + { + child_allocation.height = MAX (1, child_size - child->padding * 2); + child_allocation.y = y + child->padding; + } + else + { + child_allocation.height = sizes[packing][i].minimum_size; + child_allocation.y = y + (child_size - child_allocation.height) / 2; + } + + if (packing == GTK_PACK_START) + { + y += child_size + priv->spacing; + } + else + { + y -= child_size + priv->spacing; + child_allocation.y -= child_size; + } + } + gtk_widget_size_allocate_with_baseline (child->widget, &child_allocation, baseline); + + i++; + } + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + side[packing] = x; + else + side[packing] = y; + } + + /* Allocate the center widget */ + center_pos = (box_size - center_size) / 2; + if (center_pos < side[GTK_PACK_START]) + center_pos = side[GTK_PACK_START]; + else if (center_pos + center_size > side[GTK_PACK_END]) + center_pos = side[GTK_PACK_END] - center_size; + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + child_allocation.x = center_pos; + child_allocation.width = center_size; + } + else + { + child_allocation.y = center_pos; + child_allocation.height = center_size; + } + gtk_widget_size_allocate_with_baseline (priv->center->widget, &child_allocation, baseline); +} + +static void +gtk_box_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkBox *box = GTK_BOX (widget); + + if (box->priv->center && + gtk_widget_get_visible (box->priv->center->widget)) + gtk_box_size_allocate_with_center (widget, allocation); + else + gtk_box_size_allocate_no_center (widget, allocation); +} + static GType gtk_box_child_type (GtkContainer *container) { @@ -1046,7 +1423,7 @@ box_child_visibility_notify_cb (GObject *obj, gtk_box_invalidate_order (box); } -static void +static GtkBoxChild * gtk_box_pack (GtkBox *box, GtkWidget *child, gboolean expand, @@ -1084,6 +1461,8 @@ gtk_box_pack (GtkBox *box, gtk_widget_child_notify (child, "pack-type"); gtk_widget_child_notify (child, "position"); gtk_widget_thaw_child_notify (child); + + return child_info; } static void @@ -1103,6 +1482,7 @@ gtk_box_get_size (GtkWidget *widget, gint minimum_below, natural_below; gboolean have_baseline; gint min_baseline, nat_baseline; + gint center_min, center_nat; box = GTK_BOX (widget); private = box->priv; @@ -1115,6 +1495,8 @@ gtk_box_get_size (GtkWidget *widget, nvis_children = 0; + center_min = center_nat = 0; + for (children = private->children; children; children = children->next) { GtkBoxChild *child = children->data; @@ -1136,13 +1518,21 @@ gtk_box_get_size (GtkWidget *widget, { if (private->homogeneous) { - gint largest; - - largest = child_minimum + child->padding * 2; - minimum = MAX (minimum, largest); - - largest = child_natural + child->padding * 2; - natural = MAX (natural, largest); + if (child == private->center) + { + center_min = child_minimum + child->padding * 2; + center_nat = child_natural + child->padding * 2; + } + else + { + gint largest; + + largest = child_minimum + child->padding * 2; + minimum = MAX (minimum, largest); + + largest = child_natural + child->padding * 2; + natural = MAX (natural, largest); + } } else { @@ -1176,8 +1566,16 @@ gtk_box_get_size (GtkWidget *widget, { if (private->homogeneous) { - minimum *= nvis_children; - natural *= nvis_children; + if (center_min > 0) + { + minimum = minimum * (nvis_children - 1) + center_min; + natural = natural * (nvis_children - 1) + center_nat; + } + else + { + minimum *= nvis_children; + natural *= nvis_children; + } } minimum += (nvis_children - 1) * private->spacing; natural += (nvis_children - 1) * private->spacing; @@ -2063,6 +2461,9 @@ gtk_box_remove (GtkContainer *container, { gboolean was_visible; + if (priv->center == child) + priv->center = NULL; + g_signal_handlers_disconnect_by_func (widget, box_child_visibility_notify_cb, box); @@ -2107,16 +2508,25 @@ gtk_box_forall (GtkContainer *container, child = children->data; children = children->next; + if (child == priv->center) + continue; + if (child->pack == GTK_PACK_START) (* callback) (child->widget, callback_data); } + if (priv->center) + (* callback) (priv->center->widget, callback_data); + children = g_list_last (priv->children); while (children) { child = children->data; children = children->prev; + if (child == priv->center) + continue; + if (child->pack == GTK_PACK_END) (* callback) (child->widget, callback_data); } @@ -2145,3 +2555,47 @@ _gtk_box_get_children (GtkBox *box) return g_list_reverse (retval); } + +/** + * gtk_box_set_center_widget: + * @box: a #GtkBox + * @widget: the widget to center + * + * Sets a center widget; that is a child widget that will be + * centered with respect to the full width of the box, even + * if the children at either side take up different amounts + * of space. + * + * Since: 3.12 + */ +void +gtk_box_set_center_widget (GtkBox *box, + GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_BOX (box)); + + box->priv->center = gtk_box_pack (box, widget, + FALSE, TRUE, 0, + GTK_PACK_START); +} + +/** + * gtk_box_get_center_widget: + * @box: a #GtkBox + * + * Retrieves the center widget of the box. + * + * Return value: the center widget + * + * Since: 3.12 + */ +GtkWidget * +gtk_box_get_center_widget (GtkBox *box) +{ + g_return_val_if_fail (GTK_IS_BOX (box), NULL); + + if (box->priv->center) + return box->priv->center->widget; + + return NULL; +} diff --git a/gtk/gtkbox.h b/gtk/gtkbox.h index bed154794b..9f99afbcaf 100644 --- a/gtk/gtkbox.h +++ b/gtk/gtkbox.h @@ -129,6 +129,12 @@ void gtk_box_set_child_packing (GtkBox *box, guint padding, GtkPackType pack_type); +GDK_AVAILABLE_IN_3_12 +void gtk_box_set_center_widget (GtkBox *box, + GtkWidget *widget); +GDK_AVAILABLE_IN_3_12 +GtkWidget *gtk_box_get_center_widget (GtkBox *box); + G_END_DECLS #endif /* __GTK_BOX_H__ */ -- 2.30.2